<script setup>
import { computed, ref, reactive } from 'vue'
import uniIcons from '@dcloudio/uni-ui/lib/uni-icons/uni-icons.vue'
import { useStore } from 'vuex'
import { transcriptAudio, uploadFile } from '@/api/chat'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import { intervalToDuration } from 'date-fns'
import AudioRecorder from '@/utils/recorder'
import Compressor from 'compressorjs'
import UploadLoading from '@/components/UploadLoading.vue'
import {
  useActiveElement,
  useMagicKeys,
  whenever,
  onKeyStroke,
} from '@vueuse/core'
import useLocale from '@/composables/useLocale'
import { isMobile } from '@/utils/common'
import useAIModelStore from '@/store/modules/AIModel'
import index from '../ChatOptionsPopover/index.vue'

defineProps({
  focus: {
    type: Boolean,
    default: false,
  },
})

const AIModelStore = useAIModelStore()
const AIModel = computed(() => AIModelStore.AIModel)

const emit = defineEmits(['sendMessage', 'focus', 'blur', 'abortGenerating'])

const store = useStore()

const userInput = ref('')
const messageInputLeftVisible = ref(true)
const isGenerating = computed(() => store.state.chat.isGenerating)
const isGeneratingImage = computed(() => store.state.chat.isGeneratingImage)
const isConverting = ref(false)

const uploadFiles = ref([])

const { t } = useLocale()

// #ifdef WEB
const keys = useMagicKeys({
  passive: false,
  onEventFired(event) {
    // console.log('onEventFired', event)
    // if (event.key === 'Enter') {

    //   const inputValue = event.targe.value
    //   debugger;
    //   if (
    //     inputValue.includes('\n') || // 检查是否包含换行符
    //     inputValue.startsWith(' ') || // 检查是否以空格开头
    //     inputValue.endsWith(' ') || // 检查是否以空格结尾
    //     /[^\u4e00-\u9fa5]/.test(inputValue)
    //   ) {
    //     // 检查是否包含非中文字符
    //     // 有可能来自输入法
    //     console.log('回车键可能来自输入法')
    //     return
    //   }
    // }

    if (event.key === 'Enter' && event.type === 'keydown') {
      event.preventDefault()
    }
  },
})

onKeyStroke(['Enter'], (event) => {
  if (event.key === 'Enter' && !event.ctrlKey) {
    if (usingTextarea.value) {
      if (isGenerating.value) {
        emit('abortGenerating')
      } else {
        handleSendMessage()
      }
    }
  }
  event.preventDefault()
})

const ctrlEnter = keys['ctrl+enter']
// const enter = keys['enter']
// const ctrl = keys['ctrl']
const activeElement = useActiveElement()
const usingTextarea = computed(
  () => activeElement.value?.tagName === 'TEXTAREA',
)
// whenever(enter, (event) => {
//   console.log('event', event)

//   // if (usingTextarea.value && !ctrl.value) {
//   //   if (isGenerating.value) {
//   //     emit('abortGenerating')
//   //   } else {
//   //     handleSendMessage()
//   //   }
//   // }
// })
whenever(ctrlEnter, () => {
  if (usingTextarea.value) {
    userInput.value += '\n'
  }
})
// #endif

async function handleSendMessage() {
  
  if (isGeneratingImage.value) {
    return
  }

  if (isGenerating.value) {
    emit('abortGenerating')
  }

  if (
    userInput.value.length === 0 &&
    uploadFiles.value.length === 0 
  ) {
    return
  }

  const msg = userInput.value
  const files = uploadFiles.value

  if (!isGenerating.value) {
    userInput.value = ''
    uploadFiles.value = []
  }

  store.commit('chat/setRefreshConversation', msg)

  emit('sendMessage', {
    msg,
    files: files.map(({ url, type, fileName }) => ({
      fileUrl: url,
      type: type.includes('image/') ? 1 : 2,
      fileName: fileName,
    })),
  })
}

const isRecording = ref(false)
const recordingElapsedTime = ref(0)

function handleFocus(e) {
  messageInputLeftVisible.value = false
  emit('focus', e)
}

function handleBlur() {
  // #ifdef MP-WEIXIN
  emit('blur')
  // #endif
}

let recordingInterval = null

// #ifdef WEB
const recorder = new AudioRecorder()
// #endif

// #ifdef MP-WEIXIN
const recorderManager = uni.getRecorderManager()
// #endif

onLoad(() => {
  // #ifdef MP-WEIXIN
  recorderManager.onStop(async (res) => {
    isRecording.value = false
    isConverting.value = true
    const resp = await transcriptAudio({ filePath: res.tempFilePath })
    isConverting.value = false
    if (resp.statusCode === 200) {
      userInput.value = resp.data
    }
  })
  // #endif
})

async function handleRecording() {
  if (isRecording.value) {
    handleStopRecording()
    return
  }
  isRecording.value = true
  recordingInterval = setInterval(() => {
    if (isRecording.value) {
      // max recording time is 1 hour
      if (recordingElapsedTime.value >= 3600) {
        handleStopRecording()
        return
      }
      recordingElapsedTime.value += 1
    }
  }, 1000)
  // #ifdef WEB
  const supported = await recorder.start()
  if (!supported) {
    uni.showToast({
      title: '当前环境不支持录音',
      icon: 'none',
    })
    isRecording.value = false
    recordingInterval && clearInterval(recordingInterval)
    return
  }
  // #endif
  // #ifdef MP-WEIXIN
  uni.authorize({
    scope: 'scope.record',
    success: () => {
      recorderManager.start({
        format: 'mp3',
      })
    },
    fail: () => {
      uni.showToast({
        title: '请授权录音权限',
        icon: 'none',
      })
    },
  })
  // #endif
}

async function handleStopRecording() {
  isRecording.value = false
  recordingElapsedTime.value = 0
  recordingInterval && clearInterval(recordingInterval)
  // #ifdef WEB
  const blob = await recorder.stop()
  if (!blob) {
    uni.showToast({
      title: '录音失败',
      icon: 'none',
    })
    return
  }
  const file = new File([blob], 'audio.mp3', { type: 'audio/mp3' })
  isConverting.value = true
  const resp = await transcriptAudio({ file })
  isConverting.value = false
  if (resp.statusCode === 200) {
    userInput.value = resp.data
  }
  // #endif

  // #ifdef MP-WEIXIN
  recorderManager.stop()
  // #endif
}

onUnload(() => {
  if (isRecording.value) {
    handleStopRecording()
  }
})

function handleChooseImage(sourceType) {

  uni.chooseImage({
    // default is 9
    count: 9,
    sourceType: [sourceType],
    success: (res) => {
      res.tempFiles.forEach(async (file) => {
        // #ifdef WEB
        // if image size is larger than 2MB, compress it
        if (file.size > 2 * 1024 * 1024) {
          new Compressor(file, {
            quality: 0.6,
            async success(result) {
              uploadFiles.value.push({
                path: file.path,
                lastModified: file.lastModified,
                lastModifiedDate: file.lastModifiedDate,
                name: file.name,
                size: file.size,
                type: file.type,
                webkitRelativePath: file.webkitRelativePath,
                uploaded: true,
              })

              const resp = await uploadFile({ file: result })
              const uploadFile = JSON.parse(resp.data)

              uploadFiles.value = uploadFiles.value.map((item) => {
                if (item.name === uploadFile.fileName) {
                  return {
                    ...item,
                    url: result.filePath,
                    uploaded: false,
                    imageUrl: getImage(file.type, uploadFile.filePath),
                  }
                }
                return item
              })
            },
          })
          return
        }
        // #endif

        try {
          uploadFiles.value.push({
            path: file.path,
            lastModified: file.lastModified,
            lastModifiedDate: file.lastModifiedDate,
            name: file.name,
            size: file.size,
            type: file.type,
            webkitRelativePath: file.webkitRelativePath,
            uploaded: true,
          })
          const resp = await uploadFile({ filePath: file.path })
          const data = JSON.parse(resp.data)

          uploadFiles.value = uploadFiles.value.map((item) => {
            if (item.name === data.fileName) {
              return {
                ...item,
                url: data.filePath,
                uploaded: false,
                imageUrl: getImage(file.type, data.filePath),
              }
            }
            return item
          })
        } catch (e) {
          console.log('upload failed', e)
        }
      })
      messageInputLeftVisible.value = false
    },
    fail: (err) => {
      console.log('chooseImage fail', err)
    },
  })
}

const formattedRecordingTime = computed(() => {
  const duration = intervalToDuration({
    start: 0,
    end: recordingElapsedTime.value * 1000,
  })
  return `${String(duration.minutes ?? 0).padStart(2, '0')}:${String(duration.seconds ?? 0).padStart(2, '0')}`
})

const sendDisabled = computed(() => {
  return (
    (userInput.value.length === 0 &&
    uploadFiles.value.length === 0) ||
    isGeneratingImage.value
  )
})

const supportedFileTypes = [
  '.pdf',
  '.doc',
  '.txt',
  '.xls',
  '.ppt',
  '.docx',
  '.xlsx',
  '.pptx',
  '.c',
  '.cpp',
  '.h',
  '.hpp',
  '.java',
  '.py',
  '.js',
  '.ts',
  '.html',
  '.css',
  '.json',
  '.xml',
  '.php',
]
function handleUploadFiles(files) {
  files.forEach(
    async ({
      path,
      lastModified,
      lastModifiedDate,
      name,
      size,
      type,
      webkitRelativePath,
    }) => {
      try {
        uploadFiles.value.push({
          path,
          lastModified,
          lastModifiedDate,
          name,
          size,
          type,
          webkitRelativePath,
          uploaded: true,
        })
        const { data } = await uploadFile({ filePath: path })
        const result = JSON.parse(data)
        uploadFiles.value = uploadFiles.value.map((item) => {
          if (item.name === name) {
            return {
              ...item,
              url: result.filePath,
              uploaded: false,
              imageUrl: getImage(type, result.filePath),
            }
          }
          return item
        })

        console.log('uploadFiles', uploadFiles)
      } catch (e) {
        console.log('upload failed', e)
      }
    },
  )
}

const fileCountLimit = 6
function handleChooseFiles() {
  // #ifdef MP-WEIXIN
  wx.chooseMessageFile({
    count: fileCountLimit,
    type: 'file',
    success: (res) => {
      handleUploadFiles(res.tempFiles)
    },
    fail: (err) => {
      console.log('chooseFile fail', err)
    },
  })

  // #endif

  // #ifdef WEB
  uni.chooseFile({
    count: fileCountLimit,
    extension: supportedFileTypes,
    success: (res) => {
      handleUploadFiles(res.tempFiles)
    },
    fail: (err) => {
      console.log('chooseFile fail', err)
    },
  })
  // #endif
}
function handleRemoveFile({ index }) {
  uploadFiles.value = uploadFiles.value.filter((item, i) => i !== index)
}

function getImage(type, fullPath) {
  const [filType, name] = type.split('/')
  const mimeMap = {
    pdf: 'affix',
    doc: 'doc',
    txt: 'affix',
    xls: 'affix',
    ppt: 'pptx',
    docx: 'affix',
    xlsx: 'affix',
    pptx: 'affix',
    c: 'affix',
    cpp: 'affix',
    h: 'affix',
    hpp: 'affix',
    java: 'affix',
    py: 'affix',
    js: 'affix',
    ts: 'affix',
    html: 'affix',
    css: 'affix',
    json: 'affix',
    xml: 'affix',
    php: 'affix',
  }
  console.log('type', type, fullPath)
  return filType === 'image'
    ? fullPath
    : `../../public/mimes/${mimeMap[name] || 'affix'}.png`
}
</script>

<template>
  <view class="bg-white">
    <view class="relative flex items-center justify-between gap-3 px-3 py-2">
      <view
        v-if="AIModel?.name === 'GPT-4'"
        class="flex size-8 flex-shrink-0 items-center justify-center rounded-full bg-violet-100 transition-all"
        :class="{
          'opacity-100': !messageInputLeftVisible,
          'opacity-0': messageInputLeftVisible,
        }"
        @click="messageInputLeftVisible = true"
      >
        <uni-icons font-family="heroicons-outline" :size="22">
          {{ '\ue9e8' }}
        </uni-icons>
      </view>
      <view
        v-if="AIModel?.name === 'GPT-4'"
        class="absolute left-3 flex flex-shrink-0 gap-5 overflow-hidden transition-all"
        :class="{
          'w-full': messageInputLeftVisible,
          'w-0': !messageInputLeftVisible,
        }"
      >
        <uni-icons
          v-if="isMobile()"
          font-family="heroicons-outline"
          :size="24"
          @click="handleChooseImage('camera')"
        >
          {{ '\ue957' }}
        </uni-icons>
        <uni-icons
          font-family="heroicons-outline"
          :size="24"
          @click="handleChooseImage('album')"
        >
          {{ '\ue9e2' }}
        </uni-icons>
        <uni-icons
          font-family="heroicons-outline"
          :size="24"
          @click="handleChooseFiles"
        >
          {{ '\ue9aa' }}
        </uni-icons>
      </view>
      <view
        class="z-10 flex min-h-14 flex-grow flex-col justify-center rounded-3xl bg-zinc-100 px-4 py-2 transition-all"
        :class="{
          'ml-20': messageInputLeftVisible && AIModel?.name === 'GPT-4',
        }"
      >
        <view
          v-if="uploadFiles.length > 0"
          class="mb-2 flex gap-2 overflow-x-auto"
        >
          <view
            v-for="(file, idx) in uploadFiles"
            :key="idx"
            class="relative flex rounded bg-zinc-200"
          >
            <image
              class="size-20 rounded-md"
              :src="file.imageUrl"
              mode="aspectFill"
            />

            <UploadLoading v-if="file.uploaded" class="absolute inset-0" />
            <view
              class="absolute right-0.5 top-0.5 flex size-6 cursor-pointer items-center justify-center rounded-full bg-zinc-100"
            >
              <uni-icons
                font-family="heroicons-outline"
                :size="20"
                color="#09090b"
                @click="handleRemoveFile({ name: file.name, index: idx })"
              >
                {{ '\uea27' }}
              </uni-icons>
            </view>
          </view>
        </view>
        <view class="flex items-center">
          <textarea
            v-model.trim="userInput"
            class="w-full rounded-full bg-zinc-100 py-1 pl-1 pr-2 text-base"
            :placeholder="t('message')"
            :focus="focus"
            :cursor-spacing="10"
            auto-height
            :adjust-position="false"
            :show-confirm-bar="false"
            :maxlength="-1"
            @focus="handleFocus"
            @blur="handleBlur"
          />
          <uni-icons
            font-family="heroicons-solid"
            :size="20"
            :color="isRecording ? '#09090b' : '#a1a1aa'"
            @click="handleRecording"
          >
            {{ '\ue9cf' }}
          </uni-icons>
        </view>
      </view>
      <view
        class="z-10 flex size-8 shrink-0 items-center justify-center rounded-full"
        :class="{
          'bg-zinc-950': !sendDisabled || isGenerating,
          'bg-zinc-100': sendDisabled && !isGenerating,
        }"
        @click="handleSendMessage"
      >
        <uni-icons
          v-if="isGenerating"
          font-family="heroicons-solid"
          :size="18"
          color="white"
        >
          {{ '\uea0c' }}
        </uni-icons>
        <uni-icons
          v-else
          font-family="heroicons-outline"
          :size="18"
          :color="!sendDisabled ? 'white' : '#a1a1aa'"
        >
          {{ '\ue92a' }}
        </uni-icons>
      </view>
    </view>
    <view
      v-if="isRecording || isConverting"
      class="flex h-64 flex-col bg-violet-100 p-3 text-base"
    >
      <view v-if="isRecording">{{ formattedRecordingTime }}</view>
      <view
        v-if="isRecording"
        class="relative flex flex-grow items-center justify-center"
        @click="handleStopRecording"
      >
        <view
          class="absolute left-1/2 top-1/2 size-12 -translate-x-1/2 -translate-y-1/2 rounded-full bg-rose-100"
        ></view>
        <uni-icons font-family="heroicons-solid" :size="24">
          {{ '\uea0b' }}
        </uni-icons>
        <text class="relative z-10 ml-1">点击停止录制</text>
      </view>
      <view
        v-if="isConverting"
        class="flex flex-grow items-center justify-center"
      >
        识别中...
      </view>
    </view>
  </view>
</template>
